#include <fs.h>
#include "fat.h"

#include <device/block.h>

#include <mm/valloc.h>
#include <mm/slab.h>
#include <sys/unistd.h>

#define LOG_TAG "FAT"
#include <log.h>

extern inode_ops fat32_inode_ops;
extern f_ops fat32_file_ops;

kmem_cache_t* fat_inode_kmem_cache;

extern void fat32_init_inode(sb_t* sb, inode_t* inode);

int fat32_mount(sb_t* sb, dentry_t* mnt_point) {
    device_t* dev = sb->dev;
    blk_dev_t* bdev = dev->impl;
    u8* buf = valloc(bdev->blk_size);

    int errno = bdev->block_read(bdev, buf, bdev->start_lba, 1);
    if (errno != 1) {
        errno = EIO;
        goto out;
    }

    fat32_BPB_t* bpb = (fat32_BPB_t*)buf;
    if (bpb->fat_count <= 0) {
        errno = EINVAL;
    }

    fat32_extBS_t* ebs = (fat32_extBS_t*)(buf + 0x24);
    if (!(ebs->signature == 0x28) && !(ebs->signature == 0x29)) {
        errno = EINVAL;
    }

    fat_sb_t* fsb = valloc(sizeof(fat_sb_t));
    fsb->cluster_size = bpb->cluster_size * bpb->sector_size;
    fsb->fats = bpb->fat_count;
    fsb->fat_start = bpb->number_reserved_sector;
    fsb->fat_length = ebs->fat_size;
    fsb->data_start = fsb->fat_start + fsb->fats * fsb->fat_length;
    fsb->root_cluster = ebs->root_cluster;

    errno = bdev->block_read(bdev, buf, bdev->start_lba + ebs->fat_info, 1);
    if (errno != 1) {
        errno = EIO;
        vfree(fsb);
        goto out;
    }

    fat32_fsinfo_t* fsinfo = (fat32_fsinfo_t*)buf;
    if (!(fsinfo->signature1 == 0x41615252UL && fsinfo->signature2 == 0x61417272UL)) {
        errno = EINVAL;
        vfree(fsb);
        goto out;
    }
    fsb->free_clusters = fsinfo->free_clusters;
    fsb->prev_free = fsinfo->next_cluster;
    fsb->max_cluster = (bpb->number_sector - fsb->data_start) / fsb->cluster_size;

    
    sb->data = fsb;

    sb->init_inode = fat32_init_inode;
    inode_t* root = fs_alloc_inode(sb);
    root->type = FS_DIR;

    fs_inode_addhash(root);
    mnt_point->inode = root;
    fs_assign_inode(mnt_point, root);

    fat_inode_t* finode = root->data;
    finode->start = fsb->root_cluster;

    size_t nxt, count = 0;
    do {
        count++;
        fat_next_clus(root, fsb->root_cluster, &nxt);
    } while (nxt < FAT32_CLUSTER_END);
    
    root->fsize = count * fsb->cluster_size;
out:
    vfree(buf);
    return errno;
}

int fat32_umount(sb_t* sb) {
    vfree(sb->data);
    return 0;
}

void fat32_init() {
    fat_inode_kmem_cache = new_kmem_cache("fat_inode", sizeof(fat_inode_t), 1, 0);
    fs_t* fs = fs_alloc_fs("fat32", 5);
    fs->mount = fat32_mount;
    fs->umount = fat32_umount;

    fs_register(fs);
}
